package com.arpa.wms.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.IterUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.arpa.core.web.Result;
import com.arpa.ntocc.common.common.cache.PartyCache;
import com.arpa.ntocc.common.common.exception.ServiceException;
import com.arpa.ntocc.common.common.util.CommonUtil;
import com.arpa.ntocc.common.common.util.UserUtil;
import com.arpa.wms.cache.*;
import com.arpa.wms.domain.consts.PropertyConst;
import com.arpa.wms.domain.dto.InventoryDTO;
import com.arpa.wms.domain.dto.LocationDTO;
import com.arpa.wms.domain.dto.LocationExcelDTO;
import com.arpa.wms.domain.entity.*;
import com.arpa.wms.domain.enums.*;
import com.arpa.wms.domain.vo.LocationVO;
import com.arpa.wms.domain.vo.WarehouseSpaceVO;
import com.arpa.wms.mapper.LocationBearTypeMapper;
import com.arpa.wms.mapper.LocationMapper;
import com.arpa.wms.mapper.WarehouseMapper;
import com.arpa.wms.mapper.WarehouseSpaceMapper;
import com.arpa.wms.service.IInventoryService;
import com.arpa.wms.service.ILocationService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Future;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static com.arpa.wms.domain.consts.NumConst.NUM_ZERO;
import static com.arpa.wms.domain.consts.TipConst.*;

/**
 * <p>
 * 库位 服务实现类
 * </p>
 *
 * @author LEO
 * @since 2020-08-03
 */
@Service
@Log4j2
public class LocationServiceImpl extends ServiceImpl<LocationMapper, Location> implements ILocationService {

    @Autowired
    private IInventoryService iInventoryService;

    private final LocationCache locationCache;

    @Autowired
    private WarehouseCache warehouseCache;

    @Autowired
    private WarehouseSpaceCache warehouseSpaceCache;

    @Autowired
    private LocationBearTypeCache locationBearTypeCache;

    @Autowired
    private PartyCache partyCache;

    @Autowired
    private ImportTempCache importTempCache;

    @Resource
    private WarehouseMapper warehouseMapper;

    @Resource
    private WarehouseSpaceMapper warehouseSpaceMapper;

    @Resource
    private LocationBearTypeMapper locationBearTypeMapper;

    public LocationServiceImpl(LocationCache locationCache) {
        this.locationCache = locationCache;
    }

    /**
     * 保存
     *
     * @param entity
     * @return
     */
    @Override
    @Transactional(rollbackFor = {Exception.class})
    public boolean save(Location entity) {

        entity.setGroupCode(UserUtil.getBranchCode());
        Map<String,Object> warehouse =  warehouseCache.get(entity.getWarehouseCode());
        if(MapUtil.isEmpty(warehouse)){
            throw new ServiceException("库位添加失败，仓库信息找不到");
        }
        Map<String,Object> space = warehouseSpaceCache.get(entity.getSpaceCode());
        if(MapUtil.isEmpty(space)){
            throw new ServiceException("库位添加失败，仓库库区信息找不到");
        }

        entity.setFullSerialNumber(MapUtil.getStr(warehouse,"serialNumber")+"_"+MapUtil.getStr(space,"serialNumber")+"_"+entity.getSerialNumber());
        if (StringUtils.isBlank(entity.getCode())) {
            entity.setCode(IdUtil.simpleUUID());
        }
        if (StringUtils.isBlank(entity.getCreatedBy())) {
            entity.setCreatedBy(UserUtil.getCode());
            entity.setCreatedName(partyCache.translate(UserUtil.getCode()));
        }
        if (StringUtils.isBlank(entity.getModifiedBy())) {
            entity.setModifiedBy(UserUtil.getCode());
            entity.setModifiedName(partyCache.translate(UserUtil.getCode()));
        }



//        locationCache.flushCache(entity.getCode());
//        locationCache.flushCacheByGroupCode();
//        locationCache.flushCacheByWarehouseCode(entity.getWarehouseCode());
        return super.save(entity);
    }
    /**
     * 根据code更新实体
     *
     * @param entity
     * @return
     */
    @Override
    @Transactional(rollbackFor = {Exception.class})
    public int updateByCode(Location entity) {
        if (StringUtils.isBlank(entity.getModifiedBy())) {
            entity.setModifiedBy(UserUtil.getCode());
            entity.setModifiedName(partyCache.translate(UserUtil.getCode()));
        }
        locationCache.flushCache(entity.getCode());
        return baseMapper.update(entity, new QueryWrapper<Location>().lambda()
                .eq(Location::getCode, entity.getCode())
                .eq(Location::getGroupCode,entity.getGroupCode()));
    }

    /**
     * 通过code获取仓库信息
     *
     * @param code
     * @return
     */
    @Override
    public Location getByCode(String code) {
        Map<String, Object> temp = this.locationCache.get(code);
        if (MapUtil.isNotEmpty(temp)) {
            return BeanUtil.mapToBean(temp, Location.class, false);
        }
        return null;
    }


    /**
     * 通过code获取库位详细信息
     *
     * @param code
     * @return
     */
    @Override
    public LocationVO getLocation(String code) {
        LocationVO location = null;
        Map<String, Object> temp = this.locationCache.get(code);

        if (MapUtil.isNotEmpty(temp)) {
            location = BeanUtil.mapToBean(temp, LocationVO.class, false);
            if (StrUtil.isNotBlank(location.getWarehouseCode())) {
                location.setWarehouseName(warehouseCache.translate(location.getWarehouseCode()));
            }
            if (StrUtil.isNotBlank(location.getSpaceCode())) {
                location.setSpaceName(warehouseSpaceCache.translate(location.getSpaceCode()));
            }
            if (StrUtil.isNotBlank(location.getBearType())) {
                location.setBearTypeName(locationBearTypeCache.translate(location.getBearType()));
            }

            if (StrUtil.isNotBlank(location.getLocationType())) {
                LocationTypeEnum locationTypeEnum = LocationTypeEnum.getEnumByValue(location.getLocationType());
                if (locationTypeEnum != null) {
                    location.setLocationTypeName(locationTypeEnum.getDesc());
                }
            }
            if (StrUtil.isNotBlank(location.getIoLock())) {
                LocationIOLockEnum locationIOLockEnum = LocationIOLockEnum.getEnumByValue(location.getIoLock());
                if (locationIOLockEnum != null) {
                    location.setIoLockName(locationIOLockEnum.getDesc());
                }
            }
            if (StrUtil.isNotBlank(location.getStorageStrategy())) {
                StorageStrategyEnum storageStrategyEnum = StorageStrategyEnum.getEnumByValue(location.getStorageStrategy());
                if (storageStrategyEnum != null) {
                    location.setStorageStrategyName(storageStrategyEnum.getDesc());
                }
            }

        }
        return location;
    }

    /**
     * 查询列表
     *
     * @param locationDTO
     * @return
     */
    @Override
    public List<LocationVO> queryList(LocationDTO locationDTO) {
        //排序字段名需要驼峰转数据库下划线类型字段名
        if (StringUtils.isNotEmpty(locationDTO.getSortField())) {
            locationDTO.setSortField(CommonUtil.camel2Underline(locationDTO.getSortField()));
            if (!locationDTO.getSortField().equals("warehouse_name")) {
            } else {
                locationDTO.setSortField("a.warehouse_code");
            }
            if (!locationDTO.getSortField().equals("space_name")) {
            } else {
                locationDTO.setSortField("a.space_code");
            }
            if (!locationDTO.getSortField().equals("bear_type_name")) {
            } else {
                locationDTO.setSortField("a.bear_type");
            }
        }
        List<LocationVO> locationVOList = baseMapper.queryList(locationDTO);
        return locationVOList;
    }

    /**
     * 通过承载类型查询库位信息
     * @param list
     * @return
     */
    @Override
    public List<LocationVO> queryByBearType(List<String> list) {

        return baseMapper.queryByBearType(list);
    }

    /**
     * 查询合计,包含总数
     *
     * @param locationDTO
     * @return
     */
    @Override
    public LocationVO queryListSum(LocationDTO locationDTO) {
        LocationVO locationVO = baseMapper.queryListSum(locationDTO);
        return locationVO;
    }


    /**
     * 删除库位
     *
     * @param codes
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public Result deleteByCode(List<String> codes) {

        Map<String, Object> result = this.batchdel(codes);
        Integer success = MapUtil.getInt(result, PropertyConst.SUCCESS);
        int failNum = codes.size() - success;
        StringBuilder builder = new StringBuilder();
        builder.append("删除成功" + success + "条；失败" + failNum + "条；");
        return Result.ok(builder.toString());

    }


    /**
     * 根据code更新启用、停用状态
     *
     * @param codes
     * @return
     */
    @Override
    public int activeStatus(List<String> codes) {
        int num = this.changeStatus(codes, StatusEnum.ACTIVE, DeleteFlagEnum.NOTDELETE.getCode().toString());
        return num;
    }

    /**
     * 根据code更新启用、停用状态
     *
     * @param
     * @return
     */
    /*@Override
    @Async
    @Transactional(rollbackFor = {Exception.class})
    public Future<Boolean> active(String code) {
        log.info("库位启用启动线程" + Thread.currentThread().getName() + " 执行异步启用库位任务：" + code);
        //Location  entity = super.getOne(new QueryWrapper<Location>().lambda().eq(Location::getCode, code));
        //if (null == entity) {
        //    log.error("启用库位【{}】失败，找不到库位信息", code);
        //    throw new ServiceException("启用库位【" + code + "】失败，找不到库位信息");
        //}
        //
        //if (StrUtil.equals(entity.getStatus(), StatusEnum.ACTIVE.getValue())) {
        //    log.error("启用库位【{}】失败，库位已启用，不能重复启用", code);
        //    throw new ServiceException("启用库位【" + entity.getSerialNumber() + "】失败，不能重复启用");
        //}

        boolean updateStatus = super.update(new UpdateWrapper<Location>().lambda().eq(Location::getCode, code)
                .set(Location::getStatus, StatusEnum.ACTIVE)
        );

        this.locationCache.flushCache(code);
        log.info("启用库位【{}】，启用结果【{}】", code, updateStatus);
        return new AsyncResult(true);
    }*/

    @Override
    public int changeStatusByWarehouseCodes(List<String> warehouseCodes, String status, String deleteFlag) {
        return baseMapper.changeStatusByWarehouseCodes(warehouseCodes, status, deleteFlag,UserUtil.getBranchCode());
    }

    /**
     * 根据库位code更新启用、停用状态
     *
     * @param codes
     * @return
     */
    @Transactional(rollbackFor = {Exception.class})
    @Override
    public Map<String, Object> unactiveStatus(List<String> codes) {
        Map<String, Object> result;
        result = new HashMap<>();
        int num = 0;
        //获取0库存的库位code
        List<String> emptyLocationCodes = this.baseMapper.emptyLocation(codes,UserUtil.getBranchCode());
        if (IterUtil.isNotEmpty(emptyLocationCodes)) {
            num = this.changeStatus(emptyLocationCodes, StatusEnum.UNACTIVE, "");
            //this.locationCache.flushCacheByGroupCode();
        }
        /* 库存不为0的 */
        /*List<String> collect = codes.stream().filter(r -> !emptyLocationCodes.contains(r)).collect(Collectors.toList());
        List<String> useName = collect.stream().map(info -> {
            return locationCache.translate(info);
        }).collect(Collectors.toList());
        result.put("useName", useName);*/
        result.put(PropertyConst.SUCCESS, num);
        return result;
    }

    /**
     * 未使用的库位
     *
     * @param codes
     * @return
     */
    @Override
    public List<String> emptyLocation(List<String> codes) {
        return this.baseMapper.emptyLocation(codes,UserUtil.getBranchCode());
    }

    /**
     * 库位停用
     *
     * @param code
     * @return
     */
    @Override
    @Async
    @Transactional(rollbackFor = {Exception.class})
    public Future<Boolean> unactive(String code) {
        log.info("库位停用启动线程" + Thread.currentThread().getName() + " 执行异步停用库位任务：" + code);
        boolean updateStatus = super.update(new UpdateWrapper<Location>().lambda().eq(Location::getCode, code).set(Location::getStatus, StatusEnum.UNACTIVE)
        );

        this.locationCache.flushCache(code);
        log.info("停用库位【{}】，停用结果【{}】", code, updateStatus);
        return new AsyncResult(true);
    }

    /**
     * 校验库位
     *
     * @param entity
     * @return
     */
    @Override
    public Result validateLocation(Location entity) {
        // 必填项校验
        if (StrUtil.isBlank(entity.getWarehouseCode())) {
            return Result.error("所属仓库不能为空");
        }
        if (StrUtil.isBlank(entity.getSpaceCode())) {
            return Result.error("所属库区不能为空");
        }
        if (StrUtil.isBlank(entity.getSerialNumber())) {
            return Result.error("库位编码不能为空");
        }
        if (StrUtil.isBlank(entity.getLocationType())) {
            return Result.error("库位类型不能为空");
        }
        if (StrUtil.isBlank(entity.getBearType())) {
            return Result.error("库位承载类型不能为空");
        }
        if (StrUtil.isBlank(entity.getStorageStrategy())) {
            return Result.error("混放策略不能为空");
        }
        if (StrUtil.isBlank(entity.getIoLock())) {
            return Result.error("出入库锁不能为空");
        }

       //库位新增的时候，检验库位编码是否重复
       if(StrUtil.isEmpty(entity.getCode())) {
           int num = this.count(new LambdaQueryWrapper<Location>().eq(Location::getWarehouseCode, entity.getWarehouseCode())
                   .eq(Location::getSerialNumber, entity.getSerialNumber()).eq(Location::getGroupCode, UserUtil.getBranchCode()));
           if (num > 0) {
               return Result.error("存在相同的库位编码，请确认后重新操作。");
           }
       }
        return Result.ok();
    }

    /**
     * 变更状态
     *
     * @param codes  标识
     * @param status 状态
     * @return
     */
    private int changeStatus(List<String> codes, StatusEnum status, String deleteFlag) {
        int num = 0;
        String groupCode = UserUtil.getBranchCode();
        if (IterUtil.isNotEmpty(codes)) {
            List<Location> locations = this.list(new LambdaQueryWrapper<Location>()
                    .select(Location::getCode,Location::getStatus)
                    .in(Location::getCode, codes)
                    .eq(Location::getGroupCode, groupCode));

            if (status.getValue().equals(StatusEnum.ACTIVE.getValue())) {
                //状态为停用的库位
                List<Location> unActivedLocation = locations.stream().filter(e -> !e.getStatus().equals(StatusEnum.ACTIVE.getValue())).collect(Collectors.toList());
                //待停用的code集合
                List<String> unActicedCodes = unActivedLocation.stream().map(Location::getCode).collect(Collectors.toList());
                if (IterUtil.isNotEmpty(unActicedCodes)) {
                    this.update(new UpdateWrapper<Location>().lambda().set(Location::getStatus, status).set(Location::getDeleteFlag, DeleteFlagEnum.NOTDELETE.getCode())
                            .in(Location::getCode, unActicedCodes).eq(Location::getGroupCode, groupCode));
                }
                num = unActicedCodes.size();
            } else {
                //状态为启用的库位
                List<Location> activedLocation = locations.stream().filter(e -> e.getStatus().equals(StatusEnum.ACTIVE.getValue())).collect(Collectors.toList());
                //待停用的code集合
                List<String> acticedCodes = activedLocation.stream().map(Location::getCode).collect(Collectors.toList());
                if (IterUtil.isNotEmpty(acticedCodes)) {
                    this.update(new UpdateWrapper<Location>().lambda().set(Location::getStatus, status)
                            .in(Location::getCode, acticedCodes).eq(Location::getGroupCode, groupCode));
                }
                num = acticedCodes.size();
            }
        }

        return num;
    }
    /**
     * 前台页面仓库下拉数据，需要过滤掉临时库位
     *
     * @return
     */
    @Override
    public List<LocationVO> findLocationLimit(String warehouseCode, String spaceCode, String locationName,String pageNum,String pageSize) {
        Map<String,Object> map;
        map = new HashMap<>();
        map.put("warehouseCode",warehouseCode);
        map.put("spaceCode",spaceCode);
        map.put("groupCode",UserUtil.getBranchCode());
        map.put("serialNumber",locationName);
        map.put("status","0");
        map.put("tempLocation",0);
        map.put("pageNum",Integer.valueOf(pageNum));
        map.put("pageSize",Integer.valueOf(pageSize));
        List<LocationVO> locationVOs = this.baseMapper.getAllByMap(map);
        return locationVOs;
    }

    /**
     * 前台页面仓库下拉数据，需要过滤掉临时库位
     *
     * @return
     */
    @Override
    public List<LocationVO> findLocation(String warehouseCode, String spaceCode, String locationName) {
        Map<String,Object> map;
        map = new HashMap<>();
        map.put("warehouseCode",warehouseCode);
        map.put("spaceCode",spaceCode);
        map.put("groupCode",UserUtil.getBranchCode());
        map.put("serialNumber",locationName);
        map.put("status","0");
        map.put("tempLocation",0);
        map.put("pageNum","");
        map.put("pageSize","");
        List<LocationVO> locationVOs = this.baseMapper.getAllByMap(map);
        return locationVOs;
    }

    /**
     * 查询合计,包含总数
     * @param
     * @return
     */
    @Override
    public LocationVO queryListSumLocation(String warehouseCode, String spaceCode, String locationName, String pageNum, String pageSize){
        Map<String,Object> map;
        map = new HashMap<>();
        map.put("warehouseCode",warehouseCode);
        map.put("spaceCode",spaceCode);
        map.put("groupCode",UserUtil.getBranchCode());
        map.put("serialNumber",locationName);
        map.put("status","0");
        map.put("tempLocation",0);
        map.put("pageNum",pageNum);
        map.put("pageSize",pageSize);
        LocationVO locationVO = baseMapper.queryListSumLocation(map);
        return locationVO;
    }

    /**
     * 批量删除
     *
     * @param codes
     */
    @Transactional(rollbackFor = {Exception.class})
    @Override
    public Map<String, Object> batchdel(List<String> codes) {
        Map<String, Object> result;
        result = new HashMap<>();
        List<Location> locations = this.list(new LambdaQueryWrapper<Location>().in(Location::getCode, codes));
        //可删除code
        List<String> deleteCodes = locations.stream().filter(info -> Integer.valueOf(info.getDeleteFlag()).intValue() == DeleteFlagEnum.CANDELETE.getCode().intValue()).map(info -> {
            return info.getCode();
        }).collect(Collectors.toList());
        //删除掉可以删除的库位
        if (deleteCodes.size() > 0) {
            this.remove(new LambdaQueryWrapper<Location>().in(Location::getCode, deleteCodes));
        }
        result.put(PropertyConst.SUCCESS, deleteCodes.size());
        return result;
    }

   /* *//**
     * 批量删除
     *
     * @param codes
     *//*
    @Transactional(rollbackFor = {Exception.class})
    @Override
    public Map<String, Object> batchdel(List<String> codes) {
        Map<String, Object> result;
        result = new HashMap<>();
        List<LocationVO> locations = queryByCodes(codes);
        Map<String, String> warehouseMap = locations.stream().collect(Collectors.toMap(LocationVO::getCode, LocationVO::getSerialNumber));
        //不可删除code
        List<String> notDeleteCodes = locations.stream().filter(info -> info.getDeleteFlag().intValue() == DeleteFlagEnum.NOTDELETE.getCode().intValue()).map(info -> {
            return info.getCode();
        }).collect(Collectors.toList());
        List<String> notDeleteName = notDeleteCodes.stream().map(code -> {
            return warehouseMap.get(code);
        }).collect(Collectors.toList());
        result.put("notDelete", notDeleteName);
        List<String> tempCode = codes.stream().filter(code -> !notDeleteCodes.contains(code)).collect(Collectors.toList());
        List<String> tempNoDeleteCode = new ArrayList<>();
        List<String> inventorynotDeleteName = new ArrayList<>();
        tempCode.stream().forEach(code -> {
            InventoryDTO inventoryDTO = new InventoryDTO();
            inventoryDTO.setWarehouseCode(code);
            inventoryDTO.setGroupCode(UserUtil.getBranchCode());
            BigDecimal quantity = iInventoryService.queryInventorySum(inventoryDTO);
            if (quantity.compareTo(BigDecimal.ZERO) != 0) {
                tempNoDeleteCode.add(code);
                inventorynotDeleteName.add(warehouseMap.get(code));
            }
        });
        result.put("inInventory", inventorynotDeleteName);
        int num = 0;
        if (IterUtil.isNotEmpty(tempCode)) {
            List<String> deleteCodes = tempCode.stream().filter(code -> !tempNoDeleteCode.contains(code)).collect(Collectors.toList());
            if (IterUtil.isNotEmpty(deleteCodes)) {
                num = delete(deleteCodes);
            }
        }
        result.put(PropertyConst.SUCCESS, num);
        return result;
    }*/


    private int delete(List<String> list) {
        int num = 0;
        if (IterUtil.isNotEmpty(list)) {
            for (String code : list) {
                boolean flag = this.update(new UpdateWrapper<Location>().lambda().eq(Location::getCode, code)
                        .set(Location::getDeleted, 1));
                if (flag) {
                    num++;
                    this.locationCache.flushCache(code);
                }
            }
        }
        return num;
    }

    /**
     * 创建临时库位
     *
     * @param space
     * @return
     */
    @Transactional(rollbackFor = {Exception.class})
    @Override
    public int createTempLocation(WarehouseSpace space) {
        Location location;
        List<Location> locationList = new ArrayList<Location>();
        // 收货临时库位
        location = createTempLocationTemplate(space);
        location.setSerialNumber(TempLocationTypeEnum.SH.getValue());
        locationList.add(location);
//        save(location);

        // 发货临时库位
        location = createTempLocationTemplate(space);
        location.setSerialNumber(TempLocationTypeEnum.FH.getValue());
//        save(location);
        locationList.add(location);

        // 移位临时库位
        location = createTempLocationTemplate(space);
        location.setSerialNumber(TempLocationTypeEnum.MO.getValue());
//        save(location);
        locationList.add(location);

        // 退减临时库位
        location = createTempLocationTemplate(space);
        location.setSerialNumber(TempLocationTypeEnum.RE.getValue());
//        save(location);
        locationList.add(location);

        // 退减临时库位
        location = createTempLocationTemplate(space);
        location.setSerialNumber(TempLocationTypeEnum.YD.getValue());
//        save(location);
        locationList.add(location);

        saveBatch(locationList);

        return 1;
    }

    /**
     * 更新库位动碰次数
     *
     * @param locationCode 库位编码
     * @param addActionNum 增加的动碰次数
     * @return
     */
    @Override
    public boolean updateActionNum(String locationCode, int addActionNum) {

        return this.update(new UpdateWrapper<Location>().lambda()
                .eq(Location::getGroupCode,UserUtil.getBranchCode())
                .eq(Location::getCode, locationCode)
                .setSql(" action_num = action_num + " + addActionNum));
    }

    /**
     * 前台页面库位下拉数据（包含临时库位）
     *
     * @param warehouseCode
     * @param spaceCode
     * @param locationName
     * @return
     */
    @Override
    public List<LocationVO> findAllLocation(String warehouseCode, String spaceCode, String locationName) {
        Map<String,Object> map;
        map = new HashMap<>();
        map.put("warehouseCode",warehouseCode);
        map.put("spaceCode",spaceCode);
        map.put("serialNumber",locationName);
        map.put("status","0");
        List<LocationVO> locationVOs = this.baseMapper.getAllByMap(map);
        return locationVOs;
    }

    /**
     * 获取库位导入操作预览数据
     *
     * @param list
     * @return
     */
    @Override
    @Transactional(rollbackFor = {Exception.class})
    public List<LocationVO> importPreviewList(List<LocationExcelDTO> list) {
        String groupCode = UserUtil.getBranchCode();
        // 导入号，用于从缓存中取出
        String importCode = IdUtil.simpleUUID();
        // 检验重复项
        Map<String, String> repeatData;
        repeatData = new HashMap<>();
        //所有启用仓库
        List<Warehouse> warehouseList = warehouseMapper.selectList(new LambdaQueryWrapper<Warehouse>()
                .eq(Warehouse::getGroupCode, UserUtil.getBranchCode()).eq(Warehouse::getStatus,StatusEnum.ACTIVE.getValue()));
        Map<String, Warehouse> warehouseMap = warehouseList.stream().collect(Collectors.toMap(Warehouse::getSerialNumber, v -> v));

        // 校验库区是否存在、库区是否属于仓库
        //所有库区
        List<WarehouseSpaceVO> warehouseSpaces = warehouseSpaceMapper.findAllInfo(groupCode);
        Map<String, WarehouseSpaceVO> spaceMap = warehouseSpaces.stream().collect(Collectors.toMap(k->k.getWarehouseSerialNumber()+"#"+k.getSerialNumber()+"#"+k.getGroupCode(), v -> v));

        // 校验库位是否存在
        List<Location> locationList = this.list(new LambdaQueryWrapper<Location>().eq(Location::getGroupCode, groupCode));
        Map<String, Location> locationMap = locationList.stream().collect(Collectors.toMap(k->k.getWarehouseCode()+"#"+k.getSerialNumber(), v -> v));
        List<LocationBearType> locationBearTypes = locationBearTypeMapper.selectList(new QueryWrapper<>());
        Map<String, LocationBearType> locationBearTypeMap = locationBearTypes.stream().collect(Collectors.toMap(k->k.getName()+"#"+k.getGroupCode(), v -> v));

        // 遍历
        List<LocationVO> locationVOS = new ArrayList<>();
        list.parallelStream().forEachOrdered(item -> {
            String index = item.getNumber();
            if (StrUtil.isEmpty(index)) {
                throw new ServiceException("导入序号不能为空");
            }
            // 校验必填项是否填写
            validateImportNotBlank(item, index);
            // 校验Excel里是否有重复数据
            validateImportRepeatData(repeatData, item, index);

            String warehouseSerialNumber = item.getWarehouseSerialNumber();
            String spaceSerialNumber = item.getSpaceSerialNumber();
            LocationVO vo = new LocationVO();
            // 校验仓库
            Warehouse warehouse = MapUtil.get(warehouseMap, warehouseSerialNumber, Warehouse.class);
            if (warehouse == null) {
                throw new ServiceException(TIP_PREFIX + (index) + LINE + WAREHOUSE_SERIAL_NUMBER + NOT_EXIST + "或者仓库未启用");
            }
            WarehouseSpace space = MapUtil.get(spaceMap, warehouseSerialNumber+"#"+spaceSerialNumber+"#"+groupCode, WarehouseSpace.class);
            if (space == null) {
                throw new ServiceException(TIP_PREFIX + (index) + LINE + WAREHOUSE_SPACE_SERIAL_NUMBER + NOT_EXIST);
            }

            // 校验库位
            Location location = MapUtil.get(locationMap,warehouse.getCode() + "#"  + item.getSerialNumber(), Location.class);
            if (location != null) {
                throw new ServiceException(TIP_PREFIX + (index) + LINE + LOCATION_SERIAL_NUMBER + COMPOSE_GOODS_HAS);
            }

            // 校验库位类型
            LocationTypeEnum locationTypeEnum = LocationTypeEnum.getEnumByDesc(item.getLocationTypeName());
            if (locationTypeEnum == null) {
                throw new ServiceException(TIP_PREFIX + (index) + LINE + LOCATION_TYPE_NAME + NOT_EXIST);
            }

            // 校验库位承载类型
            LocationBearType bearTypeEnum = MapUtil.get(locationBearTypeMap, item.getBearTypeName()+"#"+groupCode, LocationBearType.class);
            //LocationBearType bearTypeEnum = locationBearTypeCache.getByName(item.getBearTypeName());
            if (bearTypeEnum == null) {
                throw new ServiceException(TIP_PREFIX + (index) + LINE + BEAR_TYPE_NAME + NOT_EXIST);
            }
            // 校验混放策略
            StorageStrategyEnum strategyEnum = StorageStrategyEnum.getEnumByDesc(item.getStorageStrategyName());
            if (strategyEnum == null) {
                throw new ServiceException(TIP_PREFIX + (index) + LINE + STORAGE_STRATEGY_NAME + NOT_EXIST);
            }
            // 校验出入库锁
            LocationIOLockEnum ioLockEnum = LocationIOLockEnum.getEnumByDesc(item.getIoLockName());
            if (ioLockEnum == null) {
                throw new ServiceException(TIP_PREFIX + (index) + LINE + IO_LOCK_NAME + NOT_EXIST);
            }
            // 校验3个动线号是否是空或者数字
            if (!isNumeric(item.getShelvesNumber())) {
                throw new ServiceException(TIP_PREFIX + (index) + LINE + SHELVES_NUMBER + FORMAT_ERROR);
            }
            if (!isNumeric(item.getPickNumber())) {
                throw new ServiceException(TIP_PREFIX + (index) + LINE + PICK_NUMBER + FORMAT_ERROR);
            }
            if (!isNumeric(item.getInventoryNumber())) {
                throw new ServiceException(TIP_PREFIX + (index) + LINE + INVENTORY_NUMBER + FORMAT_ERROR);
            }

            // 生成预览数据
            BeanUtil.copyProperties(item, vo);
            vo.setWarehouseCode(warehouse.getCode());
            vo.setWarehouseName(warehouse.getName());
            vo.setSpaceName(space.getName());
            vo.setSpaceCode(space.getCode());
            vo.setLocationType(locationTypeEnum.getValue());
            vo.setBearType(bearTypeEnum.getCode());
            vo.setStorageStrategy(strategyEnum.getValue());
            vo.setIoLock(ioLockEnum.getValue());
            vo.setImportCode(importCode);
            vo.setFullSerialNumber(warehouseSerialNumber+"_"+spaceSerialNumber+"_"+item.getSerialNumber());
            vo.setGroupCode(UserUtil.getBranchCode());
            locationVOS.add(vo);
        });
        /* 保存到缓存中 */
        importTempCache.putTempData(importCode, locationVOS);

        return locationVOS;
    }

    /**
     * 根据导入码导入库位信息
     *
     * @param importCode
     * @return
     */
    @Override
    @Transactional(rollbackFor = {Exception.class})
    public Result importLocation(String importCode) {
        // 根据导入码，获取导入缓存
        List<Location> importLocationCache = importTempCache.getTempData(importCode, Location.class);
        // 判断数据，如果没有了，则直接返回
        if (importLocationCache == null) {
            throw new ServiceException("缓存数据不存在，或超过有效时间，缓存已被清除，请重新导入。");
        }

        importBatchSave(importLocationCache);
        /*
         批量导入
        batchImportSaveOrUpdate(importLocationCache);
         更新库位缓存
                locationCache.flushCacheByGroupCode();
         完成导入后删除导入的缓存
        */

        importTempCache.removeCache(importCode);

        return Result.ok();
    }

    @Override
    public Location getTempLocation(String warehouseCode, TempLocationTypeEnum tempLocationType) {
        return this.baseMapper.selectOne(new QueryWrapper<Location>().lambda()
                .eq(Location::getWarehouseCode, warehouseCode)
                .eq(Location::getGroupCode,UserUtil.getBranchCode())
                .eq(Location::getSerialNumber, tempLocationType));
    }

    @Override
    public List<LocationVO> getAllByMap(Map<String, Object> map) {
        return baseMapper.getAllByMap(map);
    }

    /**
     * 导入时，根据库位编码判断是插入还是更新
     *
     * @param importLocationCache
     */
    /*@Transactional(rollbackFor = {Exception.class})
    public void batchInsertOrUpdate(List<Location> importLocationCache) {
        // 插入数据列表、更新数据列表
        List<Location> insertList = Lists.newArrayList();
        List<Location> updateList = Lists.newArrayList();

        // 涉及到的仓库code
        Map<String, Boolean> warehouseCodes = new HashMap<>();
        // 获取涉及到的仓库下所有的库位缓存，key值为“仓库code-库区code-库位serialNumber”
        Map<String, Location> warehouseLocations = locationCache.getLocationMap(importLocationCache);
        // 遍历待导入的缓存，根据库位缓存判断是插入数据还是更新数据
        importLocationCache.stream().forEach(e -> {
            // 查询数据库，判断是否有相同库位编码数据
            Location sameOne = warehouseLocations.get(e.getWarehouseCode() + "-" + e.getSpaceCode() + "-" + e.getSerialNumber());
            if (sameOne == null) {
                // 没有相同数据，添加必要数据，插入
                e.setStatus(StatusEnum.UNACTIVE.getValue());
                insertList.add(e);
            } else {
                // 有相同数据，执行更新操作
                BeanUtil.copyProperties(e, sameOne, CopyOptions.create().setIgnoreNullValue(true));
                updateList.add(sameOne);
            }
            warehouseCodes.put(e.getWarehouseCode(), true);
        });

        // 拼接sql，批量执行新增、更新操作
        if (IterUtil.isNotEmpty(insertList)) {
            importBatchSave(insertList);
        }
        if (IterUtil.isNotEmpty(updateList)) {
            importBatchUpdate(updateList);
        }
        // 清理涉及到的仓库的库位缓存，并更新缓存
        warehouseCodes.forEach((warehouseCode, flag) -> {
            locationCache.flushCacheByWarehouseCode(warehouseCode);
            locationCache.refreshByWarehouseCode(warehouseCode);
        });
    }*/

    /**
     * 导入时，根据库位编码判断是插入还是更新
     *
     * @param importLocationCache
     */
  /*  @Transactional(rollbackFor = {Exception.class})
    public void batchImportSaveOrUpdate(List<Location> importLocationCache) {
        // 插入数据列表、更新数据列表
        List<Location> insertList = Lists.newArrayList();
        List<Location> updateList = Lists.newArrayList();

        // 涉及到的仓库code
        Map<String, Boolean> warehouseCodes = new HashMap<>();
        // 获取涉及到的仓库下所有的库位缓存，key值为“仓库code-库区code-库位serialNumber”
        Map<String, Location> warehouseLocations = locationCache.getLocationMap(importLocationCache);
        // 遍历待导入的缓存，根据库位缓存判断是插入数据还是更新数据
        importLocationCache.stream().forEach(e -> {
            // 查询数据库，判断是否有相同库位编码数据
            Location sameOne = warehouseLocations.get(e.getWarehouseCode() + "-" + e.getSpaceCode() + "-" + e.getSerialNumber());
            if (sameOne == null) {
                // 没有相同数据，添加必要数据，插入
                e.setStatus(StatusEnum.UNACTIVE.getValue());
                insertList.add(e);
            } else {
                // 有相同数据，执行更新操作
                BeanUtil.copyProperties(e, sameOne, CopyOptions.create().setIgnoreNullValue(true));
                updateList.add(sameOne);
            }
            warehouseCodes.put(e.getWarehouseCode(), true);
        });

        // 拼接sql，批量执行新增、更新操作
        if (IterUtil.isNotEmpty(insertList)) {
            importBatchSave(insertList);
        }
        if (IterUtil.isNotEmpty(updateList)) {
            importBatchUpdate(updateList);
        }
        // 清理涉及到的仓库的库位缓存，并更新缓存
        warehouseCodes.forEach((warehouseCode, flag) -> {
            locationCache.flushCacheByWarehouseCode(warehouseCode);
            locationCache.refreshByWarehouseCode(warehouseCode);
        });
    }*/

    /**
     * 导入-批量保存操作
     * @param locations
     * @return
     */
    private int importBatchSave(List<Location> locations) {
        String createdBy = UserUtil.getCode();
        String createdName = partyCache.translate(createdBy);
        LocalDateTime nowTime = LocalDateTime.now();
        StringBuilder data = new StringBuilder();
        data.append("INSERT INTO `wms_location`(`code`,`group_code`,`serial_number`,`full_serial_number`, `warehouse_code`, `space_code`, "
                + "`location_type`, `bear_type`, `shelves_number`, `pick_number`, `inventory_number`, `storage_strategy`, "
                + "`io_lock`, `remarks`, `status`, `is_deleted`, `created_by`, `gmt_created`, `modified_by`,"
                + " `gmt_modified`, `created_name`, `modified_name`, `is_temp_location`, `action_num`, `is_occupy`) values ");
        String sql = locations.stream().map(e -> makeInsertValueStr(e, createdBy, createdName, nowTime)).collect(Collectors.joining(","));
        sql+=" ON DUPLICATE KEY UPDATE " +
                "location_type=values(location_type)," +
                "bear_type=values(bear_type)," +
                "shelves_number=values(shelves_number)," +
                "pick_number=values(pick_number)," +
                "inventory_number=values(inventory_number)," +
                "storage_strategy=values(storage_strategy)," +
                "io_lock=values(io_lock)," +
                "remarks=values(remarks)," +
                "created_by=values(created_by)";
        data.append(sql);
        return this.baseMapper.execSql(data.toString());
    }

    /**
     * 导入-批量插入生成sql
     * @param location
     * @param createdBy
     * @param createdName
     * @param nowTime
     * @return
     */
    private StringBuilder makeInsertValueStr(final Location location, final String createdBy, final String createdName,
                                            LocalDateTime nowTime) {
        StringBuilder data = new StringBuilder();
        data.append("(");
        // code
        data.append("'").append(IdUtil.simpleUUID()).append("', ");
        // group_code
        data.append("'").append(UserUtil.getBranchCode()).append("', ");
        /*
         name
        data.append("'").append(ObjectUtil.defaultIfNull(location.getName(), StrUtil.EMPTY)).append("', ");
         serial_number
        */
        data.append("'").append(location.getSerialNumber()).append("', ");
        // full_serial_number
        data.append("'").append(location.getFullSerialNumber()).append("', ");
        // warehouse_code
        data.append("'").append(location.getWarehouseCode()).append("', ");
        // space_code
        data.append("'").append(location.getSpaceCode()).append("', ");
        // location_type
        data.append("'").append(location.getLocationType()).append("', ");
        // bear_type
        data.append("'").append(location.getBearType()).append("', ");
        // shelves_number
        data.append(location.getShelvesNumber()).append(", ");
        // pick_number
        data.append(location.getPickNumber()).append(", ");
        // inventory_number
        data.append(location.getInventoryNumber()).append(", ");
        // storage_strategy
        data.append("'").append(location.getStorageStrategy()).append("', ");
        // io_lock
        data.append("'").append(location.getIoLock()).append("', ");
        // remarks
        data.append("'").append(StrUtil.blankToDefault(location.getRemarks(), StrUtil.EMPTY)).append("', ");
        // status
        data.append("'").append(StatusEnum.UNACTIVE.getValue()).append("', ");
        // is_deleted
        data.append("'").append(NUM_ZERO).append("', ");
        // created_by
        data.append("'").append(createdBy).append("', ");
        // gmt_created
        data.append("'").append(nowTime).append("', ");
        // modified_by
        data.append("'").append(createdBy).append("', ");
        // gmt_modified
        data.append("'").append(nowTime).append("', ");
        // created_name
        data.append("'").append(createdName).append("', ");
        // modified_name
        data.append("'").append(createdName).append("', ");
        // is_temp_location
        data.append("'").append(NUM_ZERO).append("', ");
        // action_num
        data.append("'").append(NUM_ZERO).append("', ");
        // is_occupy
        data.append("'").append(NUM_ZERO).append("' ");
        data.append(")");
        return data;
    }


    /**
     * 导入-批量更新
     * @param locations
     * @return
     */
    private int importBatchUpdate(List<Location> locations) {
        String createdBy = UserUtil.getCode();
        String createdName = partyCache.translate(createdBy);
        LocalDateTime nowTime = LocalDateTime.now();
        StringBuilder data = new StringBuilder();
        data.append("REPLACE INTO `wms_location`(`id`, `code`, `name`, `serial_number`, `warehouse_code`, `space_code`, "
                + "`location_type`, `bear_type`, `shelves_number`, `pick_number`, `inventory_number`, `storage_strategy`, "
                + " `io_lock`, `remarks`, `status`, `is_deleted`, `created_by`, `gmt_created`, `modified_by`,"
                + " `gmt_modified`, `created_name`, `modified_name`, `is_temp_location`, `action_num`, `is_occupy`) values ");
        String sql = locations.stream().map(e -> makeUpdateValueStr(e, createdBy, createdName, nowTime)).collect(Collectors.joining(","));
        data.append(sql);
        return this.baseMapper.execSql(data.toString());
    }

    /**
     * 导入-批量更新生成sql
     * @param location
     * @param createdBy
     * @param createdName
     * @param nowTime
     * @return
     */
    private StringBuilder makeUpdateValueStr(final Location location, final String createdBy, final String createdName, LocalDateTime nowTime) {
        StringBuilder data = new StringBuilder();
        data.append("(");
        // id
        data.append(location.getId()).append(",");
        // code
        data.append("'").append(location.getCode()).append("', ");
        // name
        data.append("'").append(ObjectUtil.defaultIfNull(location.getName(), StrUtil.EMPTY)).append("', ");
        // serial_number
        data.append("'").append(location.getSerialNumber()).append("', ");
        // warehouse_code
        data.append("'").append(location.getWarehouseCode()).append("', ");
        // space_code
        data.append("'").append(location.getSpaceCode()).append("', ");
        // location_type
        data.append("'").append(location.getLocationType()).append("', ");
        // bear_type
        data.append("'").append(location.getBearType()).append("', ");
        // shelves_number
        data.append(location.getShelvesNumber()).append(", ");
        // pick_number
        data.append(location.getPickNumber()).append(", ");
        // inventory_number
        data.append(location.getInventoryNumber()).append(", ");
        // storage_strategy
        data.append("'").append(location.getStorageStrategy()).append("', ");
        // io_lock
        data.append("'").append(location.getIoLock()).append("', ");
        // remarks
        data.append("'").append(StrUtil.blankToDefault(location.getRemarks(), StrUtil.EMPTY)).append("', ");
        /*
         delete_flag 使用数据库默认值
         data.append("'").append().append("', ");
         status
        */
        data.append("'").append(location.getStatus()).append("', ");
        // is_deleted
        data.append("'").append(NUM_ZERO).append("', ");
        // created_by
        data.append("'").append(location.getCreatedBy()).append("', ");
        // gmt_created
        data.append("'").append(location.getGmtCreated()).append("', ");
        // modified_by
        data.append("'").append(createdBy).append("', ");
        // gmt_modified
        data.append("'").append(nowTime).append("', ");
        // created_name
        data.append("'").append(location.getCreatedName()).append("', ");
        // modified_name
        data.append("'").append(createdName).append("', ");
        // is_temp_location
        data.append("'").append(location.getTempLocation()).append("', ");
        // action_num
        data.append("'").append(location.getActionNum()).append("', ");
        // is_occupy
        data.append("'").append(location.getOccupy()).append("' ");
        data.append(")");
        return data;
    }



    /**
     * 校验导入数据必填项是否有空值
     *
     * @param dto
     */
    private void validateImportNotBlank(LocationExcelDTO dto, String index) {
        if (StrUtil.isBlank(dto.getWarehouseSerialNumber())) {
            throw new ServiceException(TIP_PREFIX + index + LINE + WAREHOUSE_SERIAL_NUMBER + NOT_BLANK);
        }

        if (StrUtil.isBlank(dto.getSpaceSerialNumber())) {
            throw new ServiceException(TIP_PREFIX + index + LINE + WAREHOUSE_SPACE_SERIAL_NUMBER + NOT_BLANK);
        }

        if (StrUtil.isBlank(dto.getSerialNumber())) {
            throw new ServiceException(TIP_PREFIX + index + LINE + LOCATION_SERIAL_NUMBER + NOT_BLANK);
        }

        if (StrUtil.isBlank(dto.getLocationTypeName())) {
            throw new ServiceException(TIP_PREFIX + index + LINE + LOCATION_TYPE_NAME + NOT_BLANK);
        }

        if (StrUtil.isBlank(dto.getBearTypeName())) {
            throw new ServiceException(TIP_PREFIX + index + LINE + BEAR_TYPE_NAME + NOT_BLANK);
        }

        if (StrUtil.isBlank(dto.getStorageStrategyName())) {
            throw new ServiceException(TIP_PREFIX + index + LINE + STORAGE_STRATEGY_NAME + NOT_BLANK);
        }

        if (StrUtil.isBlank(dto.getIoLockName())) {
            throw new ServiceException(TIP_PREFIX + index + LINE + IO_LOCK_NAME + NOT_BLANK);
        }
    }



    /**
     * 校验Excel中是否有重复数据
     *
     * @param repeatData
     * @param dto
     * @param index
     */
    private void validateImportRepeatData(Map<String, String> repeatData, LocationExcelDTO dto, String index) {
        String key = dto.getWarehouseSerialNumber() + "&" + dto.getSpaceSerialNumber() + "&" + dto.getSerialNumber();
        if (repeatData.containsKey(key)) {
            String preIndex = repeatData.get(key);
            throw new ServiceException(TIP_PREFIX + index + LINE + AND_NAME + TIP_PREFIX + preIndex + LINE + REPEAT_DATA);
        }
        repeatData.put(key, index);
    }


    /**
     * 创建临时库位的模板方法
     *
     * @param space
     * @return
     */
    public Location createTempLocationTemplate(WarehouseSpace space) {
        Location location = new Location();
        location.setCode(IdUtil.simpleUUID());
        location.setGroupCode(UserUtil.getBranchCode());
        location.setWarehouseCode(space.getWarehouseCode());
        location.setSpaceCode(space.getCode());
        location.setStatus(StatusEnum.ACTIVE.getValue());
        location.setTempLocation(1);
        location.setDeleted(0);
        location.setGroupCode(UserUtil.getBranchCode());
        return location;
    }


    @Override
    public List<LocationVO> queryByCodes(List<String> codes) {

        List<LocationVO> byCodes = baseMapper.getByCodes(codes,UserUtil.getBranchCode());
        byCodes.stream().forEach(info -> {
            info.setSpaceName(warehouseSpaceCache.translate(info.getSpaceCode()));
        });
        return byCodes;
    }


    /**
     * 判断是否是正整数或空字符串
     *
     * @param str
     * @return
     */
    public static boolean isNumeric(String str) {
        if (StrUtil.isBlank(str)) {
            return true;
        }
        str = str.trim();
        Pattern pattern = Pattern.compile("[0-9]*");
        return pattern.matcher(str).matches();
    }

}
